package com.wuxuan.algorithm_homework.dfsAndBfs;

import org.junit.jupiter.api.Test;

import java.util.Arrays;

/**
 * 深度优先搜索DFS
 */
public class DFS {

    private static final int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

    /**
     * 给定一个由'1'(陆地)和'0'(水)组成的二维网格地图，计算岛屿的数量。
     * 岛屿被水包围，并且通过水平或垂直方向连接相邻的陆地形成。你可以假设网格的四个边缘都被水包围。
     * <p>
     * 假设 矩阵 m * n ，m行，n列
     * 时间复杂度：O(mn)
     * 空间复杂度：O(mn)
     *
     * @param grid
     * @return
     */
    public int numIsLands(char[][] grid) {
        if (grid == null || grid.length == 0) {
            return 0;
        }

        int m = grid.length;
        int n = grid[0].length;
        boolean[][] isVisited = new boolean[m][n];
        //岛屿数量
        int count = 0;
        //遍历每一个节点
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //如果当前节点还没有进行过搜索，并且是陆地1，则进行dfs
                if (!isVisited[i][j] && grid[i][j] == '1') {
                    //传入当前节点位置，及网格的长度及其引用，isVisited的情况
                    dfs(i, j, m, n, grid, isVisited);
                    //没被搜索发现当前是未发现的岛屿，岛屿数量要加1.如果是被搜索过的，说明之前是相邻的，有被dfs搜索到，所以isVisited为true，自然不需要再count++
                    count++;
                }
            }
        }

        return count;
    }

    private void dfs(int i, int j, int m, int n, char[][] grid, boolean[][] isVisited) {
        //判断当前节点是否需要搜索
        if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0' || isVisited[i][j]) {
            return;
        }
        //需要搜索，一定要先标记一下，然后搜索其上下左右
        isVisited[i][j] = true;
        for (int[] direction : directions) {
            int x = i + direction[0];
            int y = j + direction[1];
            dfs(x, y, m, n, grid, isVisited);
        }
    }

    /**
     * 岛屿问题，被X包住的O全部改成X
     * <p>
     * 假设 矩阵 m * n ，m行，n列
     * 时间复杂度：O(mn)
     * 空间复杂度：O(mn)
     *
     * @param board
     */
    public void replaceSurroundedO(char[][] board) {
        if (board == null || board.length == 0) {
            return;
        }
        int m = board.length;
        int n = board[0].length;

        //通过 DFS 标记与边界及与边界相连的 O
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //如果遍历到网格边界的节点
                if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                    dfs(board, i, j, m, n);
                }
            }
        }

        //执行完 DFS 后，将所有未标记的 O 替换为 X，标记过的 O 恢复 为 O
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') {
                    board[i][j] = 'X';
                } else if (board[i][j] == '*') {
                    board[i][j] = 'O';
                }
            }
        }

    }

    private void dfs(char[][] board, int i, int j, int m, int n) {
        if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] != 'O') {
            return;
        }

        //标记已经遍历过的O，与常用的isVisited = true，逻辑相似，避免重复访问
        board[i][j] = '*';

        for (int[] direction : directions) {
            int x = i + direction[0];
            int y = j + direction[1];
            dfs(board, x, y, m, n);
        }

    }

    @Test
    public void testNumIsLands() {
        //示例 1:
        //输入:
        //11110
        //11010
        //11000
        //00000
        //输出:1
        char[][] grid = new char[][]{
                {'1', '1', '1', '1', '0'},
                {'1', '1', '0', '1', '0'},
                {'1', '1', '0', '0', '0'},
                {'0', '0', '0', '0', '0'},
        };
        System.out.println(numIsLands(grid));

        //示例 2:
        //输入:
        //11000
        //11000
        //00100
        //00011
        //输出:3
        char[][] grid2 = new char[][]{
                {'1', '1', '0', '0', '0'},
                {'1', '1', '0', '0', '0'},
                {'0', '0', '1', '0', '0'},
                {'0', '0', '0', '1', '1'},
        };
        System.out.println(numIsLands(grid2));
    }

    @Test
    public void testReplaceSurroundedO() {

        //示例 1:
        //2输入:
        char[][] board = new char[][]{
                {'X', 'X', 'X', 'X'},
                {'X', 'O', 'O', 'X'},
                {'X', 'X', 'O', 'X'},
                {'X', 'O', 'X', 'X'}
        };
        //输出:
//        board = new char[][]{
//                {'X', 'X', 'X', 'X'},
//                {'X', 'X', 'X', 'X'},
//                {'X', 'X', 'X', 'X'},
//                {'X', 'O', 'X', 'X'}
//        };
        replaceSurroundedO(board);
        for (char[] chars : board) {
            for (char c : chars) {
                System.out.print(c + " ");
            }
            System.out.println();
        }
    }
}
